package system;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class Game extends JPanel implements Runnable {

	private static final long serialVersionUID = 1L;

	private int screenWidth = 480;
	private int screenHeight = 270;

	private static int staticWidth = 480;   // only used to initialize Frame
	private static int staticHeight = 270;  // only used to initialize Frame

	public Color background = null;

	private long initialTime = 0;
	private long currentTime = 0;
	private long elapsedTime = 0;

	private int framerate = 30;
	private int period = 1000 / framerate;
	long startCycleTime, endCycleTime, timeDiff, sleepCycleTime;
	int overSleepTime = 0;
	int excess = 0;
	
	
	private Thread mainThread;
	private Thread renderThread;
	private Thread fastThread;
	
	int[] calls = {0,0,0};
	private boolean isRunning = false;

	// Components
	public InputManager inputs;
	public SoundLibrary jukebox;
	public static Scene currentScene;
	protected Graphics gg = null;
	protected static JFrame frame;

	// Game States
	public static final int GAME_OVER = -1;
	public static final int GAME_PAUSED = -2;
	public static final int GAME_EXIT = -3;
	public static final int GAME_HIGHSCORES = -4;
	public static final int GAME_SPLASH = -5;
	public static final int GAME_RUN = 1;
	public static final int GAME_START = 0;
	private int state = GAME_SPLASH;
	

	public Game(){
		this(0, 0, 480, 270);
	}

	public Game(int x, int y, int frameWidth, int frameHeight){

		inputs = new InputManager(this);
		jukebox = new SoundLibrary();

		this.screenWidth = frameWidth;
		this.screenHeight = frameHeight;
		setBounds(x, y, screenWidth, screenHeight);
		currentScene = null;
		setBackground(Color.BLACK);
		setFocusable(true);

		initialTime = System.currentTimeMillis();
	}

	public void start(){
		if(mainThread == null || !isRunning){
			mainThread = new Thread(this);
			mainThread.setName("mainThread");
			mainThread.setPriority(5);
			
			renderThread = new Thread(this);
			renderThread.setName("renderThread");
			renderThread.setPriority(5);
			
			fastThread = new Thread(this);
			fastThread.setName("fastThread");
			fastThread.setPriority(5);
			
			isRunning = true;
			mainThread.start();
			renderThread.start();
			fastThread.start();
		}
	}

	public static void windowStart(Game game){

		Dimension screenDims = Toolkit.getDefaultToolkit().getScreenSize();
		int x = (int) ((screenDims.getWidth() - 480)/2);
		int y = (int) ((screenDims.getHeight() - 270)/2);

		frame = new JFrame("GIGATRON");
		frame.add(game);

		Dimension d = new Dimension(staticWidth, staticHeight);
		frame.getComponent(0).setPreferredSize(d);
		frame.setResizable(false);
		frame.pack();
		frame.setLocation(x, y);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);



		game.start();
	}

	@Override
	public void paint(Graphics g){

		super.paint(g);
		currentScene.paint(g);
		// TODO: Hide / Draw Cursor

	}


	public void update(){
		
		// handle global sound updates
		if (inputs.getKeyTyped() == KeyEvent.VK_S && inputs.isPressing(inputs.KEY_CTRL)){
			jukebox.ChangeMuteState(!jukebox.IsMuted(), currentScene);
			inputs.resetKeyTyped();
		}
	
		// handle scene-specific update
		currentScene.update();

	}
	
	@Override
	public void run(){
		
		startCycleTime = System.currentTimeMillis();

		while(isRunning){
			
			if(Thread.currentThread().getName().equals("mainThread")){
				update();
			} else {
				repaint();
				
			}
			threadWait();
		}

	}

	public void threadWait(){
		endCycleTime = System.currentTimeMillis();

		// OS System Time reset workaround
		if(endCycleTime < startCycleTime){
			startCycleTime = endCycleTime;
		}

		timeDiff = endCycleTime - startCycleTime;
		sleepCycleTime = (period - timeDiff) - overSleepTime;

		currentTime = System.currentTimeMillis();
		elapsedTime = currentTime - initialTime;

		// If the cycle took shorter than our period
		if(sleepCycleTime > 0){
			try{
				Thread.sleep(sleepCycleTime);
			}
			catch(InterruptedException e){
				System.out.println("Sleep Cycle was interrupted.");
				System.exit(0);
			}
		}
		// If the cycle took LONGER than our period
		else{
			excess -= sleepCycleTime;
			overSleepTime = 0;

			Thread.yield(); // Let other threads execute
		}

		// While the excess rendering time is > our period
		startCycleTime = System.currentTimeMillis();
		while(excess > period){
			// update but don't render
			excess -= period;
			update();
		}
	}
	
	// Getters
	public int getState(){
		return state;
	}

	public long getElapsedTime(){
		return elapsedTime;
	}

	// Setters
	public void setState(int state){
		this.state = state;
	}

}
